﻿// translator.cpp : Defines the exported functions for the DLL application.
//

#include "translator.h"
#include <search.h>
#include <ObjBase.h>
#include <tchar.h>
#include  "ajson.hpp"
#include "string/strcpcvt.h"
using namespace pugi;
#include "Md5.h"
namespace SOUI {
    int StringCmp(const SStringW &str1, const SStringW &str2) {
        if(str1 == str2) {
            return 0;
        } else {
            return str1 < str2 ? -1 : 1;
        }
    }
    
    class SStrMap {
        friend class STranslatorMgr;
      public:
        SStringW strSource;
        SStringW strTranslation;
        
        static int  Compare(const void *e1, const void *e2);
        static int  CompareInSearch(const void *e1, const void *e2);
    };
    
    class SStrMapEntry {
        friend class STranslatorMgr;
      public:
        ~SStrMapEntry();
        SStringW strCtx;
        SArray<SStrMap *> m_arrStrMap;
        static int  Compare(const void *e1, const void *e2);
        static int  CompareInSearch(const void *e1, const void *e2);
    };
    
    
    int SStrMap::Compare( const void *e1, const void *e2) {
        SStrMap **p1 = (SStrMap **) e1;
        SStrMap **p2 = (SStrMap **) e2;
        return StringCmp((*p1)->strSource, (*p2)->strSource);
    }
    
    int SStrMap::CompareInSearch( const void *e1, const void *e2 ) {
        SStringW *pKey = (SStringW *)e1;
        SStrMap **p2 = (SStrMap **) e2;
        return StringCmp(*pKey, (*p2)->strSource);
    }
    
    
    int SStrMapEntry::Compare( const void *e1, const void *e2 ) {
        SStrMapEntry **p1 = (SStrMapEntry **) e1;
        SStrMapEntry **p2 = (SStrMapEntry **) e2;
        return StringCmp((*p1)->strCtx, (*p2)->strCtx);
    }
    
    int SStrMapEntry::CompareInSearch( const void *e1, const void *e2 ) {
        SStringW *pKey = (SStringW *) e1;
        SStrMapEntry **p2 = (SStrMapEntry **) e2;
        return StringCmp(*pKey, (*p2)->strCtx);
    }
    
    SStrMapEntry::~SStrMapEntry() {
        for(UINT i = 0; i < m_arrStrMap.GetCount(); i++) {
            delete m_arrStrMap.GetAt(i);
        }
    }
    
    
    //////////////////////////////////////////////////////////////////////////
    // SLang
    STranslator::STranslator() {
        m_szLangName[0] = 0;
        m_arrEntry = new SArray<SStrMapEntry *>;
    }
    
    STranslator::~STranslator() {
        for(UINT i = 0; i < m_arrEntry->GetCount(); i++) {
            delete m_arrEntry->GetAt(i);
        }
        delete m_arrEntry;
    }
    
    void STranslator::GetName(wchar_t szName[TR_MAX_NAME_LEN]) {
        wcscpy_s(szName, TR_MAX_NAME_LEN, m_szLangName);
    }
    
    
    bool STranslator::NameEqual(LPCWSTR pszName) {
        return wcscmp(m_szLangName, pszName) == 0;
    }
    
    SStringW STranslator::guid() {
        return m_guid;
    }
    
    BOOL STranslator::Load( LPVOID pData, UINT uType ) {
        switch(uType) {
            case LD_XML:
                return LoadFromXml((*(pugi::xml_node *)pData));
        }
        return FALSE;
    }
    
    BOOL STranslator::LoadFromBuffer(const SStringW &name, LPVOID pData, UINT size) {
        try {
            wcscpy_s(m_szLangName, TR_MAX_NAME_LEN, name);
            
            m_guid = name;// SOUI::SStrCpCvt::CvtA2W(Md5Encode::encode((const char*)pData, size).c_str());
            
            std::map<std::string, std::string> langs;
            ajson::load_from_buff(langs, (const char *)pData, size);
            
            SStrMapEntry *strMapEntry = new SStrMapEntry;
            for (auto const &kv : langs) {
                SStrMap *strMap = new SStrMap;
                strMap->strSource = SOUI::SStrCpCvt::CvtA2W(kv.first.c_str(), CP_UTF8);
                strMap->strTranslation = SOUI::SStrCpCvt::CvtA2W(kv.second.c_str(), CP_UTF8);
                
                strMap->strTranslation.Replace(L"\\n", L"\n");
                strMap->strTranslation.Replace(L"\\r", L"\r");
                strMapEntry->m_arrStrMap.Add(strMap);
            }
            
            qsort(strMapEntry->m_arrStrMap.GetData(), strMapEntry->m_arrStrMap.GetCount(), sizeof(SStrMap *), SStrMap::Compare);
            m_arrEntry->Add(strMapEntry);
        } catch (const std::exception &e) {
            SOUI::SStringW www = SOUI::SStrCpCvt::CvtA2W(e.what());
            OutputDebugString(www + L"\r\n");
        }
        return 0;
    }
    
    BOOL STranslator::LoadFromXml( pugi::xml_node xmlLang ) {
        wcscpy_s(m_szLangName, TR_MAX_NAME_LEN, xmlLang.attribute(L"name").value());
        
        m_guid = xmlLang.attribute(L"guid").value();
        
        int ctxCount = 0;
        xml_node nodeCtx = xmlLang.child(L"context");
        while(nodeCtx) {
            ctxCount++;
            nodeCtx = nodeCtx.next_sibling(L"context");
        }
        m_arrEntry->SetCount(ctxCount);
        nodeCtx = xmlLang.child(L"context");
        for(int i = 0; i < ctxCount; i++) {
            SASSERT(nodeCtx);
            int strCount = 0;
            xml_node nodeStr = nodeCtx.child(L"message");
            while(nodeStr) {
                strCount++;
                nodeStr = nodeStr.next_sibling(L"message");
            }
            
            SStrMapEntry *strMapEntry = new SStrMapEntry;
            strMapEntry->strCtx = nodeCtx.attribute(L"name").value();
            strMapEntry->m_arrStrMap.SetCount(strCount);
            nodeStr = nodeCtx.child(L"message");
            for(int j = 0; j < strCount; j++) {
                SASSERT(nodeStr);
                SStrMap *strMap = new SStrMap;
                strMap->strSource = nodeStr.child(L"source").text().get();
                strMap->strTranslation = nodeStr.child(L"translation").text().get();
                strMapEntry->m_arrStrMap.SetAt(j, strMap);
                nodeStr = nodeStr.next_sibling(L"message");
            }
            qsort(strMapEntry->m_arrStrMap.GetData(), strMapEntry->m_arrStrMap.GetCount(), sizeof(SStrMap *), SStrMap::Compare);
            m_arrEntry->SetAt(i, strMapEntry);
            nodeCtx = nodeCtx.next_sibling(L"context");
        }
        
        qsort(m_arrEntry->GetData(), m_arrEntry->GetCount(), sizeof(SStrMapEntry *), SStrMapEntry::Compare);
        
        m_strFontInfo = xmlLang.attribute(L"font").as_string();
        return TRUE;
    }
    
    int STranslator::tr( const SStringW &strSrc, const SStringW &strCtx, wchar_t *pszOut, int nBufLen ) const {
        SStrMapEntry **pEntry = (SStrMapEntry **)bsearch(&strCtx, m_arrEntry->GetData(), m_arrEntry->GetCount(),
                                sizeof(SStrMapEntry *), SStrMapEntry::CompareInSearch);
        if(pEntry) {
            SStrMap **pMap = (SStrMap **)bsearch(&strSrc, (*pEntry)->m_arrStrMap.GetData(), (*pEntry)->m_arrStrMap.GetCount(),
                                                 sizeof(SStrMap *), SStrMap::CompareInSearch);
            if(pMap) {
                //从指定的上下文中查找翻译
                SStringW strRet = (*pMap)->strTranslation;
                if(pszOut == NULL) {
                    return strRet.GetLength() + 1;
                }
                
                if(nBufLen < strRet.GetLength() + 1) {
                    return -1;
                }
                
                wcscpy_s(pszOut, nBufLen, strRet);
                return strRet.GetLength() + 1;
            }
        }
        if(!strCtx.IsEmpty()) {
            //从空白上下文中查找
            return tr(strSrc, SStringW(), pszOut, nBufLen);
        }
        return FALSE;
    }
    
    SStringW STranslator::getFontInfo() const {
        return m_strFontInfo;
    }
    
    //////////////////////////////////////////////////////////////////////////
    //  STranslator
    BOOL STranslatorMgr::InstallTranslator(ITranslator *pTranslator) {
        if (m_szLangName[0] == 0) {
            pTranslator->GetName(m_szLangName);
        }
        if (!pTranslator->NameEqual(m_szLangName)) {
            return FALSE;
        }
        
        SPOSITION pos = m_lstLang->GetHeadPosition();
        while(pos) {
            ITranslator *p = m_lstLang->GetNext(pos);
            if(pTranslator->guid() == p->guid()) {
                return FALSE;
            }
        }
        m_lstLang->AddHead(pTranslator);
        pTranslator->AddRef();
        
        return TRUE;
    }
    
    BOOL STranslatorMgr::UninstallTranslator(const SStringW &id) {
        SPOSITION pos = m_lstLang->GetHeadPosition();
        while(pos) {
            SPOSITION posBackup = pos;
            ITranslator *p = m_lstLang->GetNext(pos);
            if(id == p->guid()) {
                m_lstLang->RemoveAt(posBackup);
                p->Release();
                
                return TRUE;
            }
        }
        return FALSE;
    }
    
    STranslatorMgr::STranslatorMgr( void ) {
        m_szLangName[0] = 0;
        m_lstLang = new SList<ITranslator *>;
    }
    
    STranslatorMgr::~STranslatorMgr( void ) {
        SPOSITION pos = m_lstLang->GetHeadPosition();
        while(pos) {
            ITranslator *pLang = m_lstLang->GetNext(pos);
            pLang->Release();
        }
        delete m_lstLang;
    }
    
    int STranslatorMgr::tr(const SStringW &strSrc, const SStringW &strCtx, wchar_t *pszOut, int nBufLen)  const {
        if(strSrc.IsEmpty()) {
            return 0;
        }
        SPOSITION pos = m_lstLang->GetHeadPosition();
        while(pos) {
            ITranslator *pLang = m_lstLang->GetNext(pos);
            int nRet = pLang->tr(strSrc, strCtx, pszOut, nBufLen);
            if(nRet > 0 || nRet == -1) {
                return nRet;
            }
        }
        return 0;
    }
    
    BOOL STranslatorMgr::CreateTranslator( ITranslator **ppTranslator ) {
        *ppTranslator = new STranslator;
        return TRUE;
    }
    
    void STranslatorMgr::SetLanguage(const SStringW &strLang) {
        if (wcscmp(m_szLangName, strLang) != 0) {
            SPOSITION pos = m_lstLang->GetHeadPosition();
            while (pos) {
                ITranslator *pTrans = m_lstLang->GetNext(pos);
                pTrans->Release();
            }
            m_lstLang->RemoveAll();
        }
        wcscpy_s(m_szLangName, TR_MAX_NAME_LEN, strLang);
    }
    
    void STranslatorMgr::GetLanguage(wchar_t szName[TR_MAX_NAME_LEN]) const {
        wcscpy_s(szName, TR_MAX_NAME_LEN, m_szLangName);
    }
    
    //////////////////////////////////////////////////////////////////////////
    //
    namespace TRANSLATOR {
        BOOL SCreateInstanceTrantor( IObjRef **ppTrans ) {
            *ppTrans = new STranslatorMgr;
            return TRUE;
        }
    }
}
